﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadLearn
{
    public sealed class EventAwaiter<TEventArgs> : INotifyCompletion
    {
        private ConcurrentQueue<TEventArgs> m_events = new ConcurrentQueue<TEventArgs>();
        private Action m_continuation;

        #region 状态机的调用成员
        //状态机先调用这个来获得awaiter：我们自己返回自己
        public EventAwaiter<TEventArgs> GetAwaiter() { return this; }

        //告诉状态机是否发生了任何事件
        public Boolean IsCompleted { get { return m_events.Count > 0; } }

        //状态机告诉我们以后要调用什么方法：我们把它保存起来
        public void OnCompleted(Action continuation)
        {
            Volatile.Write(ref m_continuation, continuation);
        }

        //状态机查询结果：这个await操作符的结果
        public TEventArgs GetResult()
        {
            TEventArgs e;
            m_events.TryDequeue(out e);
            return e;
        }
        #endregion

        //如果都引发了事件，多个线程可能同时调用
        public void EventRaised(Object sender,TEventArgs eventArgs)
        {
            //保存EventArgs以便从GetResult/await返回
            m_events.Enqueue(eventArgs);

            //如果有一个等待进行的延续任务，该线程会运行它
            Action continuation = Interlocked.Exchange(ref m_continuation, null);
            if (continuation != null) continuation();//恢复状态机
        }

        private static async void ShowExceptions()
        {
            var eventAwaiter = new EventAwaiter<FirstChanceExceptionEventArgs>();
            AppDomain.CurrentDomain.FirstChanceException += eventAwaiter.EventRaised;
            while (true)
            {
                Console.WriteLine("AppDomain exception:{0}", (await eventAwaiter).Exception.ToString());
            }
        }

        public static void Go()
        {
            ShowExceptions();
            
            for(int i=0;i<3;i++)
            {
                try
                {
                    switch(i)
                    {
                        case 0: throw new InvalidOperationException();
                        case 1: throw new ObjectDisposedException("");
                        case 2:throw new ArgumentOutOfRangeException();
                    }
                }
                catch { }
            }

        }

    }
}
